Skip to content

Jetson Xavier NX — USB 串口设备固定绑定指南

正确物理插入顺序:

背景与问题

在 Jetson Xavier NX 上同时使用多个 USB 转串口设备时(如 STM32 下层板 + RTK GPS),系统会按照枚举顺序分配 /dev/ttyUSB0/dev/ttyUSB1 等设备节点。这个编号不稳定:拔插顺序、上电时序、甚至 USB Hub 的状态都可能导致编号互换,进而引发程序打开了错误的串口。

本文通过 udev 规则为每个设备创建固定的符号链接,从根本上解决这一问题。

适用环境

项目配置
硬件平台NVIDIA Jetson Xavier NX
操作系统Ubuntu 22.04(arm64)
设备 ASTM32F103 下层控制板(CH340 USB-TTL)
设备 BUM982 RTK GPS 模块(CH340 USB-TTL)
驱动芯片均为 CH340,idVendor=1a86idProduct=7523

核心思路

两个设备使用相同的 CH340 芯片,idVendoridProduct 完全一致,且 CH340 通常没有唯一序列号,因此无法通过 VID/PID/Serial 三元组区分。

唯一可靠的区分方式是物理 USB 端口路径KERNELS 属性),只要每次插在同一个物理口上,路径就不会变。

操作步骤

第一步:查询设备的物理端口路径

分别插入两个设备,用 udevadm 查看各自的 USB 拓扑路径:

bash
# 查看当前已识别的串口设备
ls /dev/ttyUSB*

# 查看某个设备的详细属性(以 ttyUSB0 为例)
udevadm info -a -n /dev/ttyUSB0 | grep -E '{idVendor}|{idProduct}|{serial}|KERNELS'

输出示例(STM32,插在第一个 USB 口):

KERNELS=="ttyUSB0"
KERNELS=="1-2.4:1.0"
KERNELS=="1-2.4"              ← 这就是物理端口路径
    ATTRS{idProduct}=="7523"
    ATTRS{idVendor}=="1a86"
KERNELS=="1-2"
    ATTRS{idVendor}=="0bda"
    ATTRS{idProduct}=="5411"
...

输出示例(RTK GPS,插在第二个 USB 口):

KERNELS=="ttyUSB1"
KERNELS=="1-2.2:1.0"
KERNELS=="1-2.2"              ← 物理端口路径
    ATTRS{idVendor}=="1a86"
    ATTRS{idProduct}=="7523"
...

关键信息是第三行的 KERNELS 值(不带 :1.0 后缀的那一层),它代表设备挂载的物理 USB 口位置。记录下来:

设备物理端口路径
STM32 下层板1-2.4
RTK GPS (UM982)1-2.2

注意: 如果你的 NX 使用了 USB Hub,端口路径会是类似 1-2.4 这样的多级形式(1-2 是 Hub,.4 是 Hub 上的第几个口)。不同 Hub 或不同物理口对应的路径不同,请以实际输出为准。

第二步:编写 udev 规则

创建规则文件:

bash
sudo tee /etc/udev/rules.d/99-usb-serial.rules << 'EOF'
# STM32 下层板 (插在 USB 口 1-2.4)
SUBSYSTEM=="tty", KERNELS=="1-2.4", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="stm32", MODE="0666"

# RTK GPS UM982 (插在 USB 口 1-2.2)
SUBSYSTEM=="tty", KERNELS=="1-2.2", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="rtk_gps", MODE="0666"
EOF

规则逐字段解释:

字段含义
SUBSYSTEM=="tty"仅匹配串口类设备
KERNELS=="1-2.4"匹配物理 USB 端口路径
ATTRS{idVendor}=="1a86"CH340 芯片的厂商 ID
ATTRS{idProduct}=="7523"CH340 芯片的产品 ID
SYMLINK+="stm32"创建 /dev/stm32 符号链接
MODE="0666"所有用户可读写,免 sudo

第三步:重载规则并验证

bash
# 重新加载 udev 规则
sudo udevadm control --reload-rules

# 触发规则立即生效(无需拔插设备)
sudo udevadm trigger

# 验证符号链接
ls -la /dev/stm32 /dev/rtk_gps

预期输出:

lrwxrwxrwx 1 root root 7 Jun  2 xx:xx /dev/stm32 -> ttyUSB0
lrwxrwxrwx 1 root root 7 Jun  2 xx:xx /dev/rtk_gps -> ttyUSB1

符号链接指向的 ttyUSBx 编号可能会变,但 /dev/stm32/dev/rtk_gps 永远指向正确的物理设备。

第四步:在代码中使用固定设备名

ROS2 launch 文件或配置中,统一使用固定设备名:

python
# STM32 串口通信
serial_port = '/dev/stm32'

# RTK GPS 数据接收
gps_port = '/dev/rtk_gps'

ROS2 launch 参数示例:

python
Node(
    package='rtk_um982',
    executable='um982_node',
    parameters=[{'port': '/dev/rtk_gps', 'baud_rate': 921600}]
),
Node(
    package='base_controller',
    executable='stm32_node',
    parameters=[{'port': '/dev/stm32', 'baud_rate': 115200}]
),

排查与调试

如果符号链接没有出现,按以下顺序排查:

bash
# 1. 确认规则文件语法无误
cat /etc/udev/rules.d/99-usb-serial.rules

# 2. 手动测试规则是否匹配
udevadm test /sys/class/tty/ttyUSB0 2>&1 | grep SYMLINK

# 3. 实时监控 udev 事件(拔插设备观察)
udevadm monitor --property

# 4. 确认物理端口路径是否与规则一致
udevadm info -a -n /dev/ttyUSB0 | grep KERNELS

注意事项

  1. 端口必须固定:此方案依赖物理端口绑定,两个设备必须始终插在同一个 USB 口上,不能互换。建议在设备或线缆上做标记。

  2. USB Hub 的影响:如果通过 USB Hub 连接,端口路径会包含 Hub 层级信息。更换 Hub 或换到 Hub 的不同口都会改变路径,需要重新查询并更新规则。

  3. 有序列号的设备可以更灵活:如果你的设备有唯一序列号(通过 udevadm info 中的 ATTRS{serial} 查看),可以用序列号替代端口路径来匹配,这样即使换口也能识别。CH340 芯片通常没有序列号,所以本方案使用端口路径。

  4. MODE="0666" 的安全性:这个权限设置允许所有用户读写设备,适合开发环境。生产环境中建议改用用户组方式:

    GROUP="dialout", MODE="0660"

    并确保你的用户在 dialout 组中:sudo usermod -aG dialout $USER

  5. 重启后自动生效:udev 规则是持久化的,写入 /etc/udev/rules.d/ 后重启系统也会自动加载,无需额外配置开机启动项。

最近更新